/*
 *  (c) E.S.A.
 * Functions for socket communication
 */

#include "msg_logger.h"
#include "socket_utilities.h"

#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>

#include <iostream>
#include <string>

using namespace std;

/*
 *  Creates reusable ipv4 socket 
 */
int create_socket ()
{
	int sock = socket(PF_INET, SOCK_STREAM, 0);
	if(sock < 0){
		MSG_LOG (ERR, "could not create socket");
		return sock;
	}

	int optval = 1;
	if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0){
		MSG_LOG (WARN, "could not set socket reuse option");
	}

	return sock;
}

/*
 * SERVER_MODE: Bind the listener socket to a specified port, 
 * 		listen backlog, and to any local IP address 
 */
int listen_on_socket (int port, int backlog)
{
	sockaddr_in addr;
	int listen_sock;

	bzero (&addr, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = htonl(INADDR_ANY);
	addr.sin_port = htons(port);
	
	listen_sock = create_socket ();

	if(bind(listen_sock, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0){
		MSG_LOG (ERR, "could not bind socket");
		return -1;
	}

	if(listen(listen_sock, backlog) < 0){
		MSG_LOG (ERR, "could not listen on socket");
		return -1;
	}

	return listen_sock;
}

/*
 * SERVER_MODE: Listens for a connection to be made to this socket 
 * 	and accepts it. Returns the communication socket.
 */
int accept_connection_on_socket (int listen_socket)
{
	sockaddr_in client_address;
	socklen_t size = sizeof(client_address);

	int comm_sock = accept(listen_socket, reinterpret_cast<sockaddr*>(&client_address), &size);
	if(comm_sock < 0){
		MSG_LOG (ERR, "accept on socket failed");
		return -1;
	}

	return comm_sock;
}


/** 
 * CLIENT_MODE: Connect the client socket to a specific remote address 
 */
int connect_to_server (int sock, const std::string &remote_ip, int remote_port) 
{ 
	struct sockaddr_in serv_addr;


	bzero (&serv_addr, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(remote_port);
	inet_pton (AF_INET, remote_ip.c_str(), &serv_addr.sin_addr);

	int rez = connect (sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
	if (rez < 0) {
		MSG_LOG(ERR, "connect on socket failed");
		return -1;
	}


	return sock;
} 

/*
 *  Send data on socket
 */
int send_data_on_socket(int sock, const void* data, int len)
{

	const char* data_char = reinterpret_cast<const char*>(data);
	ssize_t totally_sent = 0, partially_sent = 0;

	while (totally_sent < len){

		partially_sent = send(sock, data_char + totally_sent, len - totally_sent, 0);
		if (partially_sent < 0){
			MSG_LOG (ERR, "data send failed");
			return -1;
		}
		
		totally_sent += partially_sent;

	}

	return 0;
}

/*
 *  Sends on the socket the message size 
 *  and afterwards the string 
 */
int send_data_len_prefixed (int sock, const string& message)
{
	uint32_t len = message.length();
	int err;

	/* Send data size */
	uint32_t nbo_len = htonl(len); //network byte order length
	err = send_data_on_socket(sock, &nbo_len, sizeof(nbo_len));
	if (err < 0)
		return -1;

	/* Send the message */
	err = send_data_on_socket(sock, message.c_str(), len);
	if(err < 0)
		return -1;

	return 0;
}

/*
 *  Receive data on socket
 */
int receive_data_on_socket (int sock, void* data, int len)
{
	ssize_t totally_read = 0, partially_read = 0;

	while (totally_read < len){
		partially_read = recv(sock, (char*)data + totally_read, len - totally_read, 0);
		if (partially_read < 0){
			MSG_LOG (ERR, "data recv failed");
			return -1;
		}
		if (partially_read == 0){
			MSG_LOG (WARN, "connection lost");
			return -1;
		}
		
		totally_read += partially_read;

	}

	return 0;
}




